package com.ylr.admin.framework.service.impl;

import com.ylr.admin.common.dto.menu.MenuDto;
import com.ylr.admin.common.dto.menu.MenuEnumDto;
import com.ylr.admin.common.enums.MenuEnum;
import com.ylr.admin.common.vo.menu.MenuFormVo;
import com.ylr.admin.common.vo.menu.MenuVo;
import com.ylr.admin.framework.entity.MenuEntity;
import com.ylr.admin.framework.mapper.MenuMapper;
import com.ylr.admin.framework.service.MenuService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ylr.base.common.constant.Constant;
import com.ylr.base.common.dto.PageDto;
import com.ylr.base.common.enums.ChannelEnum;
import com.ylr.base.common.enums.CommonEnum;
import com.ylr.base.common.enums.ModuleEnum;
import com.ylr.base.common.enums.ResultEnum;
import com.ylr.base.common.exception.YlrException;
import com.ylr.base.common.po.user.UserPo;
import com.ylr.base.common.utils.ThrottleUtils;
import com.ylr.base.common.utils.UserUtils;
import com.ylr.base.common.vo.UpdateStatusVo;
import com.ylr.base.framework.service.AsyncService;
import com.ylr.base.framework.service.SnowflakeService;
import com.ylr.config.common.enums.KeyEnum;
import com.ylr.config.framework.resolver.YcMappingResolver;
import com.ylr.redis.framework.service.RedisService;
import com.ylr.redis.framework.service.RedisStringService;
import com.ylr.redis.framework.service.RedissonService;
import lombok.AllArgsConstructor;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2023-04-21 11:27:09
 * className: MenuServiceImpl
 * version: 1.0
 * description:
 */
@Service("menuService")
@AllArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, MenuEntity> implements MenuService {

    private static final Logger log = LoggerFactory.getLogger(MenuServiceImpl.class);

    private final YcMappingResolver ycMappingResolver;

    private final SnowflakeService snowflakeService;

    private final AsyncService asyncService;

    private final RedisStringService redisStringService;

    private final RedisService redisService;

    private final RedissonService redissonService;

    private final ThrottleUtils throttleUtils;

    @Override
    public void saveMenu(MenuVo menuVo) {
        // 获取正在保存菜单的用户对象
        UserPo userPo = UserUtils.getUser();
        Date date = new Date();

        MenuEntity menu = this.getMenu(menuVo, userPo, date);
        // 插入数据
        baseMapper.insert(menu);
        log.info("菜单插入完成");

    }

    @Override
    public PageDto<MenuDto> pageMenuList(MenuFormVo menuFormVo) {
        LambdaQueryWrapper<MenuEntity> menuLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 匹配条件
        menuLambdaQueryWrapper.eq(Objects.nonNull(menuFormVo.getParentId()), MenuEntity::getParentId, menuFormVo.getParentId())
                .eq(Objects.nonNull(menuFormVo.getModule()), MenuEntity::getModule, menuFormVo.getModule())
                .eq(Objects.nonNull(menuFormVo.getChannel()), MenuEntity::getChannel, menuFormVo.getChannel())
                .eq(Objects.nonNull(menuFormVo.getType()), MenuEntity::getType, menuFormVo.getType())
                .eq(Objects.nonNull(menuFormVo.getIframeStatus()), MenuEntity::getIframeStatus, menuFormVo.getIframeStatus())
                .eq(Objects.nonNull(menuFormVo.getDisplayStatus()), MenuEntity::getDisplayStatus, menuFormVo.getDisplayStatus())
                .eq(Objects.nonNull(menuFormVo.getStatus()), MenuEntity::getStatus, menuFormVo.getStatus())
                .like(Objects.nonNull(menuFormVo.getWord()), MenuEntity::getMenuName, menuFormVo.getWord())
                .eq(MenuEntity::getDeleteStatus, CommonEnum.DELETE_NO.getValue());
        // 查询数据
        List<MenuEntity> menuList = baseMapper.selectList(menuLambdaQueryWrapper);
        if (menuList.isEmpty()) {
            log.info("菜单列表查询结果为空");
            return new PageDto<>();
        }

        // 构造菜单树结构
        List<MenuDto> menuDtoList = this.makeMenuTree(menuList);

        // 分页操作
        return PageDto.pageDtoBuild(menuDtoList, menuFormVo.getPageNo(), menuFormVo.getPageSize());
    }

    @Override
    public PageDto<MenuDto> pageOneMenuList(MenuFormVo menuFormVo) {
        // mp分页对象
        Page<MenuEntity> page = new Page<>(menuFormVo.getPageNo(), menuFormVo.getPageSize());

        LambdaQueryWrapper<MenuEntity> menuLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 匹配条件
        menuLambdaQueryWrapper.eq(MenuEntity::getParentId, CommonEnum.DEFAULT_ID.getLongValue())
                .eq(MenuEntity::getDeleteStatus, CommonEnum.DELETE_NO.getValue());
        // 查询数据
        baseMapper.selectPage(page, menuLambdaQueryWrapper);

        List<MenuEntity> menuList = page.getRecords();
        long totalNum = page.getTotal();
        if (menuList.isEmpty()) {
            log.info("菜单列表查询结果为空");
            return new PageDto<>(Collections.emptyList(), totalNum, menuFormVo.getPageNo(), menuFormVo.getPageSize());
        }

        // 查询子菜单数据列表
        List<MenuEntity> childrenMenuList = this.batchSelectChildrenMenuList(menuList);

        // 合并子菜单
        menuList.addAll(childrenMenuList);

        // 构造菜单树结构
        List<MenuDto> menuDtoList = this.makeMenuTree(menuList);

        return new PageDto<>(menuDtoList, totalNum, menuFormVo.getPageNo(), menuFormVo.getPageSize());
    }

    @Override
    public MenuDto getMenu(Long id) {
        // 查询数据
        MenuEntity menu = this.findMenu(id);
        // 数据封装
        return this.getMenuDto(menu);
    }

    @Override
    public Integer getNextSort(Long parentId) {
        // 查询数据
        Integer maxSort = baseMapper.selectMaxSort(parentId, CommonEnum.DELETE_NO.getValue());
        if (Objects.isNull(maxSort)) {
            maxSort = 0;
        }
        return maxSort + 1;
    }

    @Override
    public List<MenuDto> getMenuTree(Long parentId) {

        MenuDto parentMenu = null;
        if (Objects.nonNull(parentId)) {
            LambdaQueryWrapper<MenuEntity> menuLambdaQueryWrapper = new LambdaQueryWrapper<>();
            // 查询字段
            menuLambdaQueryWrapper.select(MenuEntity::getId, MenuEntity::getParentId, MenuEntity::getParentPath, MenuEntity::getMenuName,
                    MenuEntity::getSort, MenuEntity::getCreateTime);
            // 匹配条件
            menuLambdaQueryWrapper.eq(MenuEntity::getId, parentId)
                    .eq(MenuEntity::getDeleteStatus, CommonEnum.DELETE_NO.getValue());
            // 查询数据
            List<MenuEntity> menuList = baseMapper.selectList(menuLambdaQueryWrapper);
            if (menuList.isEmpty()) {
                log.warn("父菜单数据不存在");
                throw new YlrException(ResultEnum.NOT_FOUND);
            }
            parentMenu = this.getMenuDto(menuList.get(0));
        }

        LambdaQueryWrapper<MenuEntity> menuLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查询字段
        menuLambdaQueryWrapper.select(MenuEntity::getId, MenuEntity::getParentId, MenuEntity::getParentPath, MenuEntity::getMenuName,
                MenuEntity::getSort, MenuEntity::getCreateTime);
        // 匹配条件
        menuLambdaQueryWrapper.likeRight(Objects.nonNull(parentMenu), MenuEntity::getParentPath, parentMenu.getParentPath() + parentMenu.getId() + Constant.SPLIT)
                .eq(MenuEntity::getStatus, CommonEnum.STATUS_USE.getValue())
                .eq(MenuEntity::getDeleteStatus, CommonEnum.DELETE_NO.getValue());
        // 查询数据
        List<MenuEntity> menuList = baseMapper.selectList(menuLambdaQueryWrapper);
        if (menuList.isEmpty()) {
            log.info("子菜单数据为空");
            return Collections.emptyList();
        }

        Map<String, MenuDto> menuMap = new HashMap<>();
        List<MenuDto> lastOptList = new ArrayList<>();

        menuMap.put(parentMenu.getId(), parentMenu);

        for (MenuEntity menu : menuList) {
            // 数据封装
            MenuDto menuDto = this.getMenuDto(menu);

            menuMap.put(menuDto.getId(), menuDto);

            // 查找父菜单
            MenuDto parentMenuDto = menuMap.get(menuDto.getParentId());
            if (Objects.isNull(parentMenuDto)) {
                // 找不到父菜单，延后处理
                lastOptList.add(menuDto);
            }
            // 子菜单放入父菜单的集合中
            parentMenuDto.getChildren().add(menuDto);
        }
        for (MenuDto menuDto : lastOptList) {
            // 查找父菜单
            MenuDto parentUriDto = menuMap.get(menuDto.getParentId());
            if (Objects.isNull(parentUriDto)) {
                log.warn("查找父菜单找不到。{}", menuDto);
                continue;
            }
            parentUriDto.getChildren().add(menuDto);
        }

        return Collections.singletonList(parentMenu);
    }

    @Override
    public MenuEnumDto getPageEnums() {

        MenuEnumDto menuEnumDto = new MenuEnumDto();
        menuEnumDto.setModuleEnumList(ModuleEnum.getModuleEnumList(ModuleEnum.GATEWAY.getValue()));
        menuEnumDto.setChannelEnumList(ChannelEnum.getchannelEnumList());
        menuEnumDto.setTypeEnumList(MenuEnum.getTypeEnumList());
        menuEnumDto.setIframeStatusEnumList(MenuEnum.getIframeStatusEnumList());
        menuEnumDto.setDisplayStatusEnumList(MenuEnum.getDisplayStatusEnumList());

        return menuEnumDto;
    }

    @Transactional
    @Override
    public void updateMenu(MenuVo menuVo) {

        // 1、获取菜单
        MenuEntity menu = this.findMenu(menuVo.getId());

        // 获取正在修改菜单的用户对象
        UserPo userPo = UserUtils.getUser();
        Date date = new Date();

        boolean updateParent = false;

        MenuEntity _menu = new MenuEntity();
        _menu.setId(menuVo.getId())
            .setParentId(menuVo.getParentId())
            .setModule(menuVo.getModule())
            .setChannel(menuVo.getChannel())
            .setMenuName(menuVo.getMenuName())
            .setPath(menuVo.getPath())
            .setUri(menuVo.getUri())
            .setType(menuVo.getType())
            .setIcon(menuVo.getIcon())
            .setSort(menuVo.getSort())
            .setIframeStatus(menuVo.getIframeStatus())
            .setDisplayStatus(menuVo.getDisplayStatus())
            .setUpdateTime(date)
            .setUpdateUser(userPo.getUsername())
            .setUpdateUserId(userPo.getId());
        if (!menu.getParentId().equals(_menu.getParentId())) {
            _menu.setParentPath(menuVo.getParentPath() + menuVo.getParentId() + Constant.SPLIT);

            updateParent = true;
        }
        // 2、更新数据
        baseMapper.updateById(_menu);

        // 3、更新子菜单数据
        if (updateParent) {
            String path = menu.getParentPath() + menu.getId() + Constant.SPLIT;

            List<MenuEntity> menuList = baseMapper.selectChildrenList(path, CommonEnum.DELETE_NO.getValue());
            if (menuList.isEmpty()) {
                log.info("子菜单为空");
                return;
            }
            List<MenuEntity> updateMenuList = new ArrayList<>(menuList.size());
            for (MenuEntity menuEntity : menuList) {
                String parentPath = menuEntity.getParentPath();
                // 替换父级部门路径
                parentPath = parentPath.replaceFirst(menu.getParentPath(), _menu.getParentPath());

                MenuEntity updateMenu = new MenuEntity();
                updateMenu.setId(menuEntity.getId())
                        .setParentPath(parentPath)
                        .setUpdateTime(date)
                        .setUpdateUser(userPo.getUsername())
                        .setUpdateUserId(userPo.getId());
                // 记录数据
                updateMenuList.add(updateMenu);
            }
            // 批量更新菜单
            this.updateBatchById(updateMenuList);
        }

    }

    @Override
    public void updateMenuStatus(UpdateStatusVo updateStatusVo) {
        // 获取正在修改菜单状态的用户对象
        UserPo userPo = UserUtils.getUser();
        Date date = new Date();

        LambdaUpdateWrapper<MenuEntity> menuLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        // 设置字段值
        menuLambdaUpdateWrapper.set(MenuEntity::getStatus, updateStatusVo.getStatus())
                .set(MenuEntity::getUpdateTime, date)
                .set(MenuEntity::getUpdateUser, userPo.getUsername())
                .set(MenuEntity::getUpdateUserId, userPo.getId());
        // 匹配条件
        menuLambdaUpdateWrapper.in(MenuEntity::getId, updateStatusVo.getIdList())
                .eq(MenuEntity::getDeleteStatus, CommonEnum.DELETE_NO.getValue());
        // 更新数据
        int count = baseMapper.update(null, menuLambdaUpdateWrapper);
        log.info("修改菜单状态完成。count={}", count);
        if (count == 0) {
            throw new YlrException(ResultEnum.NOT_FOUND);
        }
    }

    @Override
    public void deleteMenu(List<Long> idList) {

        // 校验删除数量是否超过最大阈值
        Integer batchMax = ycMappingResolver.getValue(KeyEnum.BATCH_OPT_NUM.getKey(), Integer.class);
        if (batchMax < idList.size()) {
            log.warn("一次删除接口数量超过阈值。batchMax={} idListSize={}", batchMax, idList.size());
            throw new YlrException(ResultEnum.BATCH_FAIL);
        }

        // 获取正在删除菜单的用户对象
        UserPo userPo = UserUtils.getUser();
        Date date = new Date();

        LambdaUpdateWrapper<MenuEntity> menuLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        // 设置字段值
        menuLambdaUpdateWrapper.set(MenuEntity::getDeleteStatus, CommonEnum.DELETE_YES.getValue())
                .set(MenuEntity::getUpdateTime, date)
                .set(MenuEntity::getUpdateUser, userPo.getUsername())
                .set(MenuEntity::getUpdateUserId, userPo.getId());
        // 匹配条件
        menuLambdaUpdateWrapper.in(MenuEntity::getId, idList)
                .eq(MenuEntity::getDeleteStatus, CommonEnum.DELETE_NO.getValue());
        // 更新数据
        int count = baseMapper.update(null, menuLambdaUpdateWrapper);
        log.info("删除菜单完成。count={}", count);
        if (count == 0) {
            throw new YlrException(ResultEnum.NOT_FOUND);
        }
    }

    /**
     * 获取菜单
     * @param id 主键
     * @return 菜单对象
     */
    private MenuEntity findMenu(Long id) {
        MenuEntity menu = baseMapper.selectById(id);
        if (Objects.isNull(menu)) {
            log.warn("菜单不存在");
            throw new YlrException(ResultEnum.NOT_FOUND);
        }
        if (CommonEnum.DELETE_YES.getValue().equals(menu.getDeleteStatus())) {
            log.warn("菜单已被删除");
            throw new YlrException(ResultEnum.NOT_FOUND);
        }
        return menu;
    }

    /**
     * 菜单参数转化成菜单实体对象
     * @param menuVo 参数对象
     * @return 菜单实体对象
     */
    private MenuEntity getMenu(MenuVo menuVo, UserPo userPo, Date date) {
        MenuEntity menu = new MenuEntity();
        menu.setId(snowflakeService.getId())
                .setParentId(menuVo.getParentId())
                .setParentPath(menuVo.getParentPath())
                .setModule(menuVo.getModule())
                .setChannel(menuVo.getChannel())
                .setMenuName(menuVo.getMenuName())
                .setPath(menuVo.getPath())
                .setUri(menuVo.getUri())
                .setUriName(menuVo.getUriName())
                .setType(menuVo.getType())
                .setIcon(menuVo.getIcon())
                .setSort(menuVo.getSort())
                .setStatus(CommonEnum.STATUS_USE.getValue())
                .setIframeStatus(menuVo.getIframeStatus())
                .setDisplayStatus(menuVo.getDisplayStatus())
                .setDeleteStatus(CommonEnum.DELETE_NO.getValue())
                .setUpdateTime(date)
                .setUpdateUser(userPo.getUsername())
                .setUpdateUserId(userPo.getId())
                .setCreateTime(date)
                .setCreateUser(userPo.getUsername())
                .setCreateUserId(userPo.getId());
        return menu;
    }

    /**
     * 菜单实体对象封装
     * @param menu 菜单对象
     * @return 封装结果
     */
    private MenuDto getMenuDto(MenuEntity menu) {
        MenuDto menuDto = new MenuDto();
        menuDto.setId(menu.getId().toString())
                .setParentId(menu.getParentId().toString())
                .setParentPath(menu.getParentPath())
                .setModule(menu.getModule())
                .setChannel(menu.getChannel())
                .setMenuName(menu.getMenuName())
                .setPath(menu.getPath())
                .setUri(menu.getUri())
                .setType(menu.getType())
                .setIcon(menu.getIcon())
                .setSort(menu.getSort())
                .setStatus(menu.getStatus())
                .setIframeStatus(menu.getIframeStatus())
                .setDisplayStatus(menu.getDisplayStatus())
                .setUpdateTime(menu.getUpdateTime())
                .setUpdateUser(menu.getUpdateUser())
                .setCreateTime(menu.getCreateTime())
                .setCreateUser(menu.getCreateUser())
                .setChildren(new TreeSet<>());
        return menuDto;
    }

    /**
     * 构造菜单树结构
     * @param menuList 菜单列表
     * @return 菜单树结构
     */
    private List<MenuDto> makeMenuTree(List<MenuEntity> menuList) {
        List<MenuDto> menuDtoList = new ArrayList<>();
        Map<String, MenuDto> menuMap = new HashMap<>();
        List<MenuDto> lastOptList = new ArrayList<>();
        for (MenuEntity menu : menuList) {
            // 菜单数据封装
            MenuDto menuDto = this.getMenuDto(menu);

            menuMap.put(menuDto.getId(), menuDto);

            // 查找父菜单
            MenuDto parentMenuDto = menuMap.get(menuDto.getParentId());
            if (Objects.isNull(parentMenuDto)) {
                if (CommonEnum.DEFAULT_ID.getStringValue().equals(menuDto.getParentId())) {
                    // 一级菜单
                    menuDtoList.add(menuDto);
                    continue;
                }
                // 找不到父菜单，延后处理
                lastOptList.add(menuDto);
            }
            parentMenuDto.getChildren().add(menuDto);
        }
        for (MenuDto menuDto : lastOptList) {
            // 查找父菜单
            MenuDto parentMenuDto = menuMap.get(menuDto.getParentId());
            if (Objects.isNull(parentMenuDto)) {
                // 仍未找到父菜单，直接加到结果列表中
                menuDtoList.add(menuDto);
                continue;
            }
            parentMenuDto.getChildren().add(menuDto);
        }
        menuDtoList.sort(Comparator.comparing(MenuDto::getSort).thenComparing(MenuDto::getCreateTime).thenComparing(MenuDto::getId));

        return menuDtoList;
    }

    /**
     * 批量查询子菜单数据
     * @param menuList 菜单数据列表
     * @return 所有子菜单数据
     */
    private List<MenuEntity> batchSelectChildrenMenuList(List<MenuEntity> menuList) {
        SqlSessionFactory sqlSessionFactory = GlobalConfigUtils.currentSessionFactory(MenuEntity.class);

        try (SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
            List<MenuEntity> childrenDeptList = new ArrayList<>();

            MenuMapper menuMapper = batchSqlSession.getMapper(MenuMapper.class);
            int size = menuList.size();
            for (int i = 0; i < size; i++) {
                MenuEntity menu = menuList.get(i);

                LambdaQueryWrapper<MenuEntity> menuLambdaWrapper = new LambdaQueryWrapper<>();
                // 匹配 父级菜单路径（右模糊）和删除状态 字段
                menuLambdaWrapper.likeRight(MenuEntity::getParentPath, menu.getParentPath() + menu.getId() + Constant.SPLIT)
                        .eq(MenuEntity::getDeleteStatus, CommonEnum.DELETE_NO.getValue());
                // 查询子菜单数据
                List<MenuEntity> newDeptList = menuMapper.selectList(menuLambdaWrapper);
                if (i % 1000 == 0) {
                    batchSqlSession.flushStatements();
                }
                childrenDeptList.addAll(newDeptList);
            }
            if (size % 1000 != 0) {
                batchSqlSession.flushStatements();
            }
            return childrenDeptList;
        } catch (Exception e) {
            log.error("批量查询子菜单数据异常。message={}", e.getMessage(), e);
            throw new YlrException();
        }
    }
}